<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref, nextTick, watch, onMounted, computed, onBeforeUnmount, reactive } from 'vue'
import { FileItem } from '../../types'
import hljs from 'highlight.js'
import { MarkdownParser } from './markdownParser'
import { outlineItemType } from '../../types'
// 文件列表相关变量
const currentFilePath = ref('')
const fileList = ref<FileItem[]>([])
const searchFileKeyWord = ref('')
const fileList_filtered = computed(() => {
  return fileList.value.filter((file) => {
    return file.filePath.toLowerCase().includes(searchFileKeyWord.value.toLowerCase())
  })
})
// 编辑区相关变量
const editorRef = ref<HTMLTextAreaElement>()
const markdownContent = ref('')
// 大纲相关变量
const searchTitleKeyWord = ref('') // 搜索大纲的关键词
const currentOutLineID = ref('0') // 当前选中的大纲项的id
const ifClickOutLine = ref(false) // 是否点击了大纲项
// 计算属性：根据markdownContent生成大纲列表
const outlineList = computed(() => {
  const originalList = MarkdownParser(markdownContent.value).outline || []
  // 使用 reactive 让大纲项的每个属性都是响应式
  return originalList.map((item) => reactive({ ...item, collapsed: false }))
})
// 计算属性：根据searchTitleKeyWord过滤大纲列表
const outlineList_filtered = computed(() => {
  let result = outlineList.value.filter((outline) => {
    return outline.text.toLowerCase().includes(searchTitleKeyWord.value.toLowerCase())
  })
  return result
})
// 文件拖拽相关变量
const isDragging = ref(false)
const dragMessage = ref('')
// 效果预览相关变量
const previewRef = ref<HTMLDivElement>()
// 菜单状态和位置
const isMenuVisible = ref(false)
const menuX = ref(0)
const menuY = ref(0)
// 操作字典
const actionDic: {
  [key: string]: {
    icon: string
    label: string
    contextMenu?: boolean
    action: () => void
  }
} = {
  h1: {
    icon: 'codex:h1',
    label: '一级标题 Ctrl+1',
    action: () => {
      addTag('#')
    }
  },
  h2: {
    icon: 'codex:h2',
    label: '二级标题 Ctrl+2',
    action: () => {
      addTag('##')
    }
  },
  h3: {
    icon: 'codex:h3',
    label: '三级标题 Ctrl+3',
    action: () => {
      addTag('###')
    }
  },
  h4: {
    icon: 'codex:h4',
    label: '四级标题 Ctrl+4',
    action: () => {
      addTag('####')
    }
  },
  h5: {
    icon: 'codex:h5',
    label: '五级标题 Ctrl+5',
    action: () => {
      addTag('#####')
    }
  },
  h6: {
    icon: 'codex:h6',
    label: '六级标题 Ctrl+6',
    action: () => {
      addTag('######')
    }
  },
  p: {
    icon: 'codex:text',
    label: '正文 Ctrl+0',
    action: () => {
      setParagraph()
    }
  },
  code: {
    icon: 'codex:brackets',
    label: '代码块 Ctrl+Shift+K',
    contextMenu: true,
    action: () => {
      insertCode()
    }
  },
  link: {
    icon: 'codex:link',
    label: '超链接 Ctrl+L',
    contextMenu: true,
    action: () => {
      inserthyperlink()
    }
  },
  quote: {
    icon: 'codex:quote',
    label: '引用 Ctrl+Q',
    contextMenu: true,
    action: () => {
      addTag('>')
    }
  },
  b: {
    icon: 'codex:bold',
    label: '加粗 Ctrl+B',
    action: () => {
      addStyle('bold')
    }
  },
  i: {
    icon: 'codex:italic',
    label: '斜体 Ctrl+I',
    action: () => {
      addStyle('italic')
    }
  },
  d: {
    icon: 'codex:strikethrough',
    label: '删除线  Ctrl+D',
    action: () => {
      addStyle('delLine')
    }
  },
  ul: {
    icon: 'codex:list-bulleted',
    label: '无序列表 Ctrl+Shift+U',
    action: () => {
      addTag('-')
    }
  },
  ol: {
    icon: 'codex:list-numbered',
    label: '有序列表 Ctrl+Shift+O',
    action: () => {
      addTag('1.')
    }
  },
  todo: {
    icon: 'codex:checklist',
    label: '待办列表 Ctrl+Shift+D',
    action: () => {
      addTag('- [ ]')
    }
  },
  table: {
    icon: 'codex:table-with-headings',
    label: '表格 Ctrl+Shift+T',
    action: () => {
      insertTable()
    }
  },
  img: {
    icon: 'codex:picture',
    label: '图片 Ctrl+Shift+I',
    action: () => {
      insertImg()
    }
  },
  video: {
    icon: 'codex:play',
    label: '视频 Ctrl+Shift+V',
    action: () => {
      insertVideo()
    }
  }
}
// 显示右键菜单 -- 编辑器
const show_edite_contextMenu = (event): void => {
  // 阻止默认右键菜单
  event.preventDefault()
  // 获取鼠标位置
  menuX.value = event.clientX
  menuY.value = event.clientY
  // 显示菜单
  isMenuVisible.value = true
}
// 隐藏编辑器右键菜单
const hide_editor_contextMenu = (): void => {
  if (isMenuVisible.value) {
    isMenuVisible.value = false
  }
}
// 按ESC时隐藏编辑器右键菜单
const ESC_hide_editor_contextMenu = ({ key }): void => {
  if (key === 'Escape') {
    hide_editor_contextMenu()
  }
}
// 切换大纲项的折叠状态
const toggleCollapse = (item: outlineItemType, index: number): void => {
  item.collapsed = !item.collapsed
  for (let i = index + 1, len = outlineList_filtered.value.length; i < len; i++) {
    const outline = outlineList_filtered.value[i]
    if (outline.level > item.level) {
      outline.hide = item.collapsed
    } else {
      break
    }
  }
}
const parsedMarkdown = computed(() => {
  return MarkdownParser(markdownContent.value).content || ''
})
const copySelectedText = (): void => {
  if (!editorRef.value) return
  const start = editorRef.value.selectionStart
  const end = editorRef.value.selectionEnd
  const content = markdownContent.value
  const selectedText = content.slice(start, end)
  navigator.clipboard.writeText(selectedText)
}
const cutSelectedText = (): void => {
  if (!editorRef.value) return
  const start = editorRef.value.selectionStart
  const end = editorRef.value.selectionEnd
  const content = markdownContent.value
  const selectedText = content.slice(start, end)
  navigator.clipboard.writeText(selectedText)
  const newContent = content.slice(0, start) + content.slice(end)
  restoreCursorAndScroll(newContent, start)
}
const paste = (): void => {
  if (!editorRef.value) return
  const start = editorRef.value.selectionStart
  const end = editorRef.value.selectionEnd
  const content = markdownContent.value
  navigator.clipboard.readText().then((text) => {
    const newContent = content.slice(0, start) + text + content.slice(end)
    restoreCursorAndScroll(newContent, start + text.length)
  })
}
// 监听 parsedMarkdown 的变化，在内容更新后执行代码高亮和添加复制按钮
watch(parsedMarkdown, () => {
  nextTick(() => {
    const codeBlocks = document.querySelectorAll('pre code')
    codeBlocks.forEach((block) => {
      hljs.highlightElement(block as HTMLElement)
      addCopyButton(block as HTMLElement)
    })
  })
})
// 更新当前高亮大纲
const update_currentOutlineId = (): void => {
  if (ifClickOutLine.value) {
    return
  }
  if (!previewRef.value) return
  const headings = previewRef.value.querySelectorAll('h1, h2, h3, h4, h5, h6')
  let lastId = ''
  for (const heading of headings) {
    // 当标题即将超出预览区域时，停止查找更新
    if ((heading as HTMLElement).offsetTop - 60 >= previewRef.value.scrollTop) {
      break
    }
    lastId = heading.id
  }
  currentOutLineID.value = lastId
}
const copyCode = (codeBlock: HTMLElement, copyButton: HTMLElement): void => {
  const text = codeBlock.textContent || ''
  navigator.clipboard
    .writeText(text)
    .then(() => {
      copyButton.textContent = '复制成功'
      setTimeout(() => {
        copyButton.textContent = '复制'
      }, 2000)
    })
    .catch(() => {
      copyButton.textContent = '复制失败'
      setTimeout(() => {
        copyButton.textContent = '复制'
      }, 2000)
    })
}
// 在代码块中添加复制按钮
const addCopyButton = (codeBlock: HTMLElement): void => {
  const preElement = codeBlock.parentElement
  if (preElement && !preElement.querySelector('.copy-button')) {
    const copyButton = document.createElement('button')
    copyButton.textContent = '复制'
    copyButton.className = 'copy-button'
    copyButton.addEventListener('click', copyCode.bind(null, codeBlock, copyButton))
    preElement.style.position = 'relative'
    preElement.appendChild(copyButton)
  }
}
// 打开文件
const openFile = (item: FileItem): void => {
  markdownContent.value = item.content
  currentFilePath.value = item.filePath
}
const isFileExists = (filePath: string): boolean => {
  return fileList.value.some((file) => file.filePath === filePath)
}
const handle_preview_click = async (event: Event): Promise<void> => {
  const target = event.target as HTMLElement
  if (target.tagName === 'A') {
    const href = target.getAttribute('href')
    if (href && href.startsWith('http')) {
      event.preventDefault()
      window.electron.ipcRenderer.send('open-link', href)
    }
  }
  // 点击待办列表，同步改动编辑区
  else if (target.tagName === 'INPUT' && (target as HTMLInputElement).type === 'checkbox') {
    const isChecked = (target as HTMLInputElement).checked
    // 找到包含待办列表项的 label 元素
    const label = target.closest('.todo-item') as HTMLLabelElement
    // 获取自定义属性 line_index
    const lineIndex = label.getAttribute('data-line_index')
    if (lineIndex) {
      const currentContent = markdownContent.value
      const lines = currentContent.split('\n')
      lines[lineIndex] = lines[lineIndex].replace(/-\s\[[ xX]\]\s/, isChecked ? '- [x] ' : '- [ ] ')
      markdownContent.value = lines.join('\n')
    }
  }
}
onMounted(() => {
  // 监听点击鼠标左键时隐藏编辑器右键菜单
  document.addEventListener('click', hide_editor_contextMenu)
  // 监听按下ESC键时隐藏编辑器右键菜单
  document.addEventListener('keydown', ESC_hide_editor_contextMenu)
  // 页面加载时从 localStorage 中初始化 fileList
  const savedFileList = localStorage.getItem('fileList')
  if (savedFileList) {
    fileList.value = JSON.parse(savedFileList)
    console.log('fileList', fileList.value)
    currentFilePath.value = fileList.value[0]?.filePath || ''
    markdownContent.value = fileList.value[0]?.content || ''
  }
  // 将目标文件从文件列表中移除
  window.electron.ipcRenderer.on('removeOut-fileList', (_, filePath) => {
    // 过滤掉要删除的文件
    fileList.value = fileList.value.filter((file) => {
      return file.filePath !== filePath
    })
    // 若移除的当前打开的文件
    if (currentFilePath.value === filePath) {
      // 若移除目标文件后，还有其他文件，则打开第一个文件
      if (fileList_filtered.value.length > 0) {
        openFile(fileList_filtered.value[0])
      } else {
        // 若移除目标文件后，没有其他文件，则清空内容和路径
        markdownContent.value = ''
        currentFilePath.value = ''
      }
    }
  })
  // 清空文件列表
  window.electron.ipcRenderer.on('clear-fileList', () => {
    fileList.value = []
    markdownContent.value = ''
    currentFilePath.value = ''
  })
  window.electron.ipcRenderer.on('do-rename-file', (_, filePath) => {
    fileList_filtered.value.forEach(async (file, index) => {
      // 找到要重命名的文件
      if (file.filePath === filePath) {
        // 将被点击的文件列表项的 contenteditable 变为 true，使其成为一个可编辑的div
        file.editable = true
        // 等待 DOM 更新
        await nextTick()
        // 全选文件列表项内的文本
        let divElement = document.getElementById(`file-${index}`)
        if (divElement) {
          const range = document.createRange()
          range.selectNodeContents(divElement) // 选择 div 内所有内容
          const selection = window.getSelection()
          if (selection) {
            selection.removeAllRanges() // 清除现有选择
            selection.addRange(range) // 添加新选择
            divElement.focus() // 聚焦到 div
          }
        }
      }
    })
  })
  window.electron.ipcRenderer.on('open-file', (_, { content, fileName, filePath }) => {
    markdownContent.value = content
    currentFilePath.value = filePath
    if (!isFileExists(filePath)) {
      fileList.value.unshift({
        content,
        fileName,
        filePath
      })
    }
  })
  window.electron.ipcRenderer.on('open-dir', (_, newFileList) => {
    // 使用 splice 方法更新数组
    fileList.value.splice(0, fileList.value.length, ...newFileList)
  })
  window.electron.ipcRenderer.on('save-file', () => {
    const content = markdownContent.value
    if (currentFilePath.value) {
      // 存在文件路径时，保存文件
      const filePath = currentFilePath.value
      // 更新文件列表内容
      fileList.value.forEach((file) => {
        if (file.filePath === filePath) {
          file.content = content
        }
      })
      window.electron.ipcRenderer.send('save-file', { content, filePath })
    } else {
      // 无文件路径时，新建文件
      window.electron.ipcRenderer.send('new-file', content)
    }
  })
})
// 监听 fileList 的变化
watch(
  fileList,
  (newValue) => {
    try {
      localStorage.setItem('fileList', JSON.stringify(newValue))
    } catch (error) {
      console.error('存储 fileList 到 localStorage 时出错:', error)
    }
  },
  { deep: true }
)
const showContextMenu = (filePath: string): void => {
  window.electron.ipcRenderer.send('showContextMenu', filePath)
  // 隐藏其他右键菜单 -- 不能同时有多个右键菜单显示
  hide_editor_contextMenu()
}
// 插入代码块
const insertCode = async (): Promise<void> => {
  if (!editorRef.value) return
  const start = editorRef.value.selectionStart
  const end = editorRef.value.selectionEnd
  const content = markdownContent.value
  const selectedText = content.slice(start, end)
  const newContent = `${content.slice(0, start)}\n${'```js'}\n${selectedText}\n${'```'}\n${content.slice(end)}`
  const newCursorPosition = start + 7 + selectedText.length
  restoreCursorAndScroll(newContent, newCursorPosition)
}
// 光标所在行前添加标记
const addTag = async (type: string): Promise<void> => {
  if (!editorRef.value) return
  const content = markdownContent.value
  const selectionStart = editorRef.value.selectionStart
  // 找到光标所在行的起始和结束位置
  let lineStart = content.lastIndexOf('\n', selectionStart - 1) + 1
  let lineEnd = content.indexOf('\n', selectionStart)
  if (lineEnd === -1) {
    lineEnd = content.length
  }
  // 获取当前行的文本
  let lineText = content.slice(lineStart, lineEnd)
  // 移除行首原有标记
  lineText = lineText.replace(/^[#>-]+\s*/, '')
  // 添加新的标记
  lineText = `${type} ${lineText.trimStart()}`
  // 构造新的内容
  const newContent = content.slice(0, lineStart) + lineText + content.slice(lineEnd)
  // 设置新的光标位置
  const newCursorPosition = lineStart + lineText.length
  restoreCursorAndScroll(newContent, newCursorPosition)
}
// 根据新内容，重新渲染页面，并恢复光标位置和滚动位置
const restoreCursorAndScroll = async (
  newContent: string,
  newCursorPosition: number
): Promise<void> => {
  // 更新文本内容
  markdownContent.value = newContent
  if (!editorRef.value) return
  const textarea = editorRef.value
  // 记录当前编辑区的滚动位置
  const originalScrollTop = textarea.scrollTop
  // 等待 DOM 更新完成
  await nextTick()
  // 重新聚焦到 textarea
  textarea.focus()
  textarea.setSelectionRange(newCursorPosition, newCursorPosition)
  // 恢复编辑区的滚动位置
  textarea.scrollTop = originalScrollTop
}
// 将光标所在行的文本设置为段落
const setParagraph = async (): Promise<void> => {
  if (!editorRef.value) return
  const content = markdownContent.value
  const selectionStart = editorRef.value.selectionStart
  // 找到光标所在行的起始和结束位置
  let lineStart = content.lastIndexOf('\n', selectionStart - 1) + 1
  let lineEnd = content.indexOf('\n', selectionStart)
  if (lineEnd === -1) {
    lineEnd = content.length
  }
  // 获取当前行的文本
  let lineText = content.slice(lineStart, lineEnd)
  // 移除行首的标题和引用标记（#>）
  lineText = lineText.replace(/^[#>]+\s*/, '')
  // 构造新的内容
  const newContent = content.slice(0, lineStart) + lineText + content.slice(lineEnd)
  // 设置新的光标位置
  const newCursorPosition = lineStart + lineText.length
  restoreCursorAndScroll(newContent, newCursorPosition)
}
// 在光标所在的位置插入超链接
const inserthyperlink = async (): Promise<void> => {
  if (!editorRef.value) return
  const textarea = editorRef.value
  const content = markdownContent.value
  const start = textarea.selectionStart
  const end = textarea.selectionEnd
  // 获取选中的文本，若未选中则默认显示 '链接文本'
  const selectedText = content.slice(start, end) || '链接文本'
  // 构造超链接的 Markdown 语法
  const hyperlink = `[${selectedText}]()`
  // 构造新的内容
  const newContent = `${content.slice(0, start)}${hyperlink}${content.slice(end)}`
  // 设置新的光标位置
  const newCursorPosition = start + hyperlink.length - 1
  restoreCursorAndScroll(newContent, newCursorPosition)
}
// 在光标所在的位置插入图片
const insertImg = async (): Promise<void> => {
  if (!editorRef.value) return
  const textarea = editorRef.value
  const content = markdownContent.value
  const start = textarea.selectionStart
  const end = textarea.selectionEnd
  let selectedText = content.slice(start, end) || '图片'
  // 构造图片的 Markdown 语法
  const hyperlink = `![${selectedText}]()`
  // 构造新的内容
  const newContent = `${content.slice(0, start)}${hyperlink}${content.slice(end)}`
  // 设置新的光标位置
  const newCursorPosition = start + hyperlink.length - 1
  restoreCursorAndScroll(newContent, newCursorPosition)
}
// 在光标所在的位置插入视频
const insertVideo = async (): Promise<void> => {
  if (!editorRef.value) return
  const textarea = editorRef.value
  const content = markdownContent.value
  const start = textarea.selectionStart
  const end = textarea.selectionEnd
  // Markdown 语法无视频，可用html实现
  const hyperlink = `<video src="" controls width="100%">
  请升级浏览器以观看视频。
</video>`
  // 构造新的内容
  const newContent = `${content.slice(0, start)}${hyperlink}${content.slice(end)}`
  // 设置新的光标位置
  const newCursorPosition = start + 12
  restoreCursorAndScroll(newContent, newCursorPosition)
}
// 给所选内容添加样式
const addStyle = async (type: string): Promise<void> => {
  if (!editorRef.value) return
  const textarea = editorRef.value
  const content = markdownContent.value
  const start = textarea.selectionStart
  const end = textarea.selectionEnd
  // 获取选中的文本
  let selectedText = content.slice(start, end)
  let defaultText = ''
  let tag = ''
  switch (type) {
    case 'bold':
      defaultText = '加粗文本'
      tag = '**'
      break
    case 'italic':
      defaultText = '斜体文本'
      tag = '*'
      break
    case 'delLine':
      defaultText = '删除线文本'
      tag = '~~'
      break
    default:
  }
  if (!selectedText) {
    selectedText = defaultText
  }
  const newText = `${tag}${selectedText}${tag}`
  // 构造新的内容
  const newContent = `${content.slice(0, start)}${newText}${content.slice(end)}`
  // 设置新的光标位置
  const newCursorPosition = start + newText.length
  restoreCursorAndScroll(newContent, newCursorPosition)
}
// 文件拖拽到编辑器中时
const handleDragOver = (e: DragEvent): void => {
  e.preventDefault()
  if (e.dataTransfer) {
    e.dataTransfer.dropEffect = 'link'
  }
  dragMessage.value = '释放鼠标打开文件（仅支持 md 文件）'
  isDragging.value = true
}
// 拖拽文件离开编辑器后
const handleDragLeave = (e): void => {
  // 确保是真正离开容器而不是子元素
  if (e.relatedTarget === null || !e.currentTarget.contains(e.relatedTarget)) {
    isDragging.value = false
  }
}
// 拖拽文件到编辑器中松开鼠标后
const handleDrop = async (event: DragEvent): Promise<void> => {
  event.preventDefault()
  const files = event.dataTransfer?.files || []
  for (const file of files) {
    file.path = window.api.getDropFilePath(file)
    if (file.type === 'text/markdown' || file.name.endsWith('.md')) {
      try {
        const content = await file.text()
        markdownContent.value = content
        currentFilePath.value = file.path
        if (!isFileExists(file.path)) {
          fileList.value.unshift({
            content,
            fileName: file.name,
            filePath: file.path
          })
        }
        // 拖入文件后，立马打开文件
        openFile({
          content,
          fileName: file.name,
          filePath: file.path
        })
      } catch (error) {
        console.error('读取文件出错:', error)
      }
    }
  }
  isDragging.value = false
}
// 编辑器按下键盘事件
const handleKeyDown = async (event: KeyboardEvent): Promise<void> => {
  // 同步预览滚动位置
  syncPreviewScroll()
  // 生成快捷键组合字符串
  const modifiers: string[] = []
  if (event.ctrlKey) modifiers.push('Ctrl')
  if (event.shiftKey) modifiers.push('Shift')
  if (event.altKey) modifiers.push('Alt')
  const key = event.key.toUpperCase()
  const shortcut = [...modifiers, key].join('+')
  // 检查是否有对应的快捷键处理函数
  if (shortcutMap[shortcut]) {
    event.preventDefault()
    await shortcutMap[shortcut]()
    return
  }
  if (event.key === 'Enter' && editorRef.value && !event.shiftKey) {
    const textarea = editorRef.value
    const content = markdownContent.value
    const cursorPosition = textarea.selectionStart
    // 找到当前行的起始位置
    const lineStart = content.lastIndexOf('\n', cursorPosition - 1) + 1
    const currentLine = content.slice(lineStart, cursorPosition)
    // 检查当前行是否为列表格式且不为空行
    const listMatch = currentLine.match(/^(\s*)([-*+]|\d+\.)\s/)
    const isLineEmpty =
      currentLine.trim().length === listMatch?.[0]?.trim().length || currentLine.trim() === ''
    // 检查当前行是否为引用格式
    const quoteMatch = currentLine.match(/^(\s*)>\s*/)
    // 检查当前行是否为待办列表格式
    const todoMatch = currentLine.match(/^(\s*)- \[[ xX]\]\s*/)
    const isTodoEmpty = currentLine.trim().length === todoMatch?.[0]?.trim().length
    if (listMatch && !isLineEmpty && !todoMatch) {
      event.preventDefault()
      const indentation = listMatch[1]
      const listMarker = listMatch[2]
      let newListMarker = listMarker
      // 若为有序列表，序号递增
      if (/^\d+\.$/.test(listMarker)) {
        const currentNumber = parseInt(listMarker.replace('.', ''), 10)
        newListMarker = `${currentNumber + 1}.`
      }
      const newContent = `${content.slice(0, cursorPosition)}\n${indentation}${newListMarker} ${content.slice(cursorPosition)}`
      // 设置新的光标位置
      const newCursorPosition = cursorPosition + indentation.length + newListMarker.length + 2
      textarea.setSelectionRange(newCursorPosition, newCursorPosition)
      restoreCursorAndScroll(newContent, newCursorPosition)
    } else if (quoteMatch) {
      event.preventDefault()
      const indentation = quoteMatch[1]
      const newContent =
        `${content.slice(0, cursorPosition)}\n${indentation}> ` + content.slice(cursorPosition)
      // 设置新的光标位置
      const newCursorPosition = cursorPosition + indentation.length + 3
      textarea.setSelectionRange(newCursorPosition, newCursorPosition)
      restoreCursorAndScroll(newContent, newCursorPosition)
    } else if (todoMatch && !isTodoEmpty) {
      event.preventDefault()
      const indentation = todoMatch[1]
      const newContent =
        `${content.slice(0, cursorPosition)}\n${indentation}- [ ] ` + content.slice(cursorPosition)
      // 设置新的光标位置
      const newCursorPosition = cursorPosition + indentation.length + 7
      textarea.setSelectionRange(newCursorPosition, newCursorPosition)
      restoreCursorAndScroll(newContent, newCursorPosition)
    }
  }
}
// 同步预览区滚动
const syncPreviewScroll = (): void => {
  // 点击大纲项时，不触发同步预览区滚动
  if (editorRef.value && previewRef.value && !ifClickOutLine.value) {
    const editor = editorRef.value
    const preview = previewRef.value
    const editorScrollRatio = editor.scrollTop / (editor.scrollHeight - editor.clientHeight)
    const previewScrollTop = editorScrollRatio * (preview.scrollHeight - preview.clientHeight)
    preview.scrollTop = previewScrollTop
  }
}
// 插入表格
const insertTable = (): void => {
  const table = '|  |  |\n|:----:|:----:|\n|  |  |\n|  |  |\n'
  const editor = editorRef.value
  if (editor) {
    const start = editor.selectionStart
    const end = editor.selectionEnd
    const before = markdownContent.value.slice(0, start)
    const after = markdownContent.value.slice(end)
    restoreCursorAndScroll(before + table + after, start + 2)
  }
}
// 快捷键映射表
const shortcutMap = {
  'Ctrl+0': setParagraph,
  'Ctrl+1': () => addTag('#'),
  'Ctrl+2': () => addTag('##'),
  'Ctrl+3': () => addTag('###'),
  'Ctrl+4': () => addTag('####'),
  'Ctrl+5': () => addTag('#####'),
  'Ctrl+6': () => addTag('######'),
  'Ctrl+Shift+K': insertCode,
  'Ctrl+L': inserthyperlink,
  'Ctrl+Q': () => addTag('>'),
  'Ctrl+B': () => addStyle('bold'),
  'Ctrl+I': () => addStyle('italic'),
  'Ctrl+D': () => addStyle('delLine'),
  'Ctrl+Shift+U': () => addTag('-'),
  'Ctrl+Shift+O': () => addTag('1.'),
  'Ctrl+Shift+D': () => addTag('- [ ]'),
  'Ctrl+Shift+T': insertTable,
  'Ctrl+Shift+V': insertVideo,
  'Ctrl+Shift+I': insertImg
}
// 点击大纲项
const handle_clickOutLine = (item): void => {
  currentOutLineID.value = item.id
  const editor = editorRef.value
  if (editor) {
    ifClickOutLine.value = true
    // 编辑区滚动滚动到指定行
    const lineHeight = parseInt(getComputedStyle(editor).lineHeight)
    const paddingTop = parseInt(getComputedStyle(editor).paddingTop)
    editor.scrollTo({
      top: paddingTop + item.index * lineHeight,
      behavior: 'smooth'
    })
  }
  const preview = previewRef.value
  if (preview) {
    const targetDom = document.getElementById(item.id)
    if (targetDom) {
      targetDom.scrollIntoView({
        behavior: 'smooth',
        block: 'start'
      })
      setTimeout(() => {
        // 延迟1s后，将 ifClickOutLine 置为 false，防止点击大纲项时，在滚动编辑区时，同步滚动预览区
        ifClickOutLine.value = false
      }, 1000)
    }
  }
}
const clearSearchFileInput = (): void => {
  searchFileKeyWord.value = ''
}
const clearSearchTitleInput = (): void => {
  searchTitleKeyWord.value = ''
}
// 重命名文件时，保存文件名
const saveFileName = async (item: FileItem, index: number): Promise<void> => {
  // 获取新的文件名，若新文件名为空，则命名为 '无标题'
  let newFileName = document.getElementById(`file-${index}`)?.textContent?.trim() || '无标题'
  // 若新文件名与旧文件名相同，则直接将被点击的文件列表项的 contenteditable 变为 false
  if (newFileName === item.fileName.replace('.md', '')) {
    item.editable = false
    return
  }
  // 拼接新的文件路径
  const newFilePath = item.filePath.replace(item.fileName, `${newFileName}.md`)
  // 开始尝试保存文件名
  const error = await window.electron.ipcRenderer.invoke('rename-file', {
    oldFilePath: item.filePath,
    newFilePath,
    newFileName
  })
  if (error) {
    // 若重命名报错，则重新聚焦，让用户重新输入文件名
    document.getElementById(`file-${index}`)?.focus()
  } else {
    // 没报错，则重命名成功，更新当前文件路径，文件列表中的文件名，文件路径，将被点击的文件列表项的 contenteditable 变为 false
    if (currentFilePath.value === item.filePath) {
      currentFilePath.value = newFilePath
    }
    item.fileName = `${newFileName}.md`
    item.filePath = newFilePath
    item.editable = false
  }
}
// 按回车时，直接失焦，触发失焦事件执行保存文件名
const saveFileName_enter = (index: number): void => {
  document.getElementById(`file-${index}`)?.blur()
}
onBeforeUnmount(() => {
  document.removeEventListener('click', hide_editor_contextMenu)
  document.removeEventListener('keydown', ESC_hide_editor_contextMenu)
  const codeBlocks = document.querySelectorAll('pre code')
  codeBlocks.forEach((block) => {
    const preElement = block.parentElement
    if (preElement) {
      const copyButtonDomList = preElement.querySelectorAll('.copy-button')
      if (copyButtonDomList) {
        copyButtonDomList.forEach((copyButton) => {
          copyButton.removeEventListener(
            'click',
            copyCode.bind(null, block as HTMLElement, copyButton as HTMLElement)
          )
        })
      }
    }
  })
})
</script>
<template>
  <div class="page">
    <!-- 右键快捷菜单--编辑器 -->
    <div
      v-if="isMenuVisible"
      class="context-menu"
      :style="{ left: `${menuX}px`, top: `${menuY}px` }"
    >
      <div class="context-menu-btnBox">
        <div class="context-menu-btn" @click="copySelectedText">
          <Icon icon="codex:copy" width="24" style="margin-right: 4px" />
          <span>复制</span>
        </div>
        <div class="context-menu-btn" @click="paste">
          <Icon icon="mingcute:paste-line" width="20" style="margin-right: 4px" />
          <span>粘贴</span>
        </div>
        <div class="context-menu-btn" @click="cutSelectedText">
          <Icon icon="tabler:cut" width="20" style="margin-right: 4px" />
          <span>剪切</span>
        </div>
      </div>
      <div class="context-menu-btnBox">
        <div class="context-menu-btn" @click="actionDic.h1.action">
          <Icon :icon="actionDic.h1.icon" width="20" />
        </div>
        <div class="context-menu-btn" @click="actionDic.h2.action">
          <Icon :icon="actionDic.h2.icon" width="20" />
        </div>
        <div class="context-menu-btn" @click="actionDic.h3.action">
          <Icon :icon="actionDic.h3.icon" width="20" />
        </div>
        <div class="context-menu-btn" @click="actionDic.p.action">
          <Icon :icon="actionDic.p.icon" width="20" />
        </div>
        <div class="context-menu-btn" @click="actionDic.b.action">
          <Icon :icon="actionDic.b.icon" width="20" />
        </div>
        <div class="context-menu-btn" @click="actionDic.d.action">
          <Icon :icon="actionDic.d.icon" width="20" />
        </div>
      </div>
      <ul>
        <template v-for="item in actionDic">
          <li v-if="item.contextMenu" :key="item.label" @click="item.action">
            <Icon :icon="item.icon" width="20" style="margin-right: 10px" />
            <span> {{ item.label }}</span>
          </li>
        </template>
      </ul>
      <div class="context-menu-btnBox">
        <div class="context-menu-btn" @click="actionDic.ul.action">
          <Icon :icon="actionDic.ul.icon" width="20" style="margin-right: 4px" />
          <span>无序</span>
        </div>
        <div class="context-menu-btn" @click="actionDic.ol.action">
          <Icon :icon="actionDic.ol.icon" width="20" style="margin-right: 4px" />
          <span>有序</span>
        </div>
        <div class="context-menu-btn" @click="actionDic.todo.action">
          <Icon :icon="actionDic.todo.icon" width="20" style="margin-right: 4px" />
          <span>待办</span>
        </div>
      </div>
      <div class="context-menu-btnBox">
        <div class="context-menu-btn" @click="actionDic.table.action">
          <Icon :icon="actionDic.table.icon" width="20" style="margin-right: 4px" />
          <span>表格</span>
        </div>
        <div class="context-menu-btn" @click="actionDic.img.action">
          <Icon :icon="actionDic.img.icon" width="20" style="margin-right: 4px" />
          <span>图片</span>
        </div>
        <div class="context-menu-btn" @click="actionDic.video.action">
          <Icon :icon="actionDic.video.icon" width="20" style="margin-right: 4px" />
          <span>视频</span>
        </div>
      </div>
    </div>
    <!-- 文件列表 -->
    <div class="dirPanel">
      <div class="panelTitle">文件列表</div>
      <div class="searchFileBox">
        <Icon class="searchFileInputIcon" icon="material-symbols-light:search" />
        <input
          v-model="searchFileKeyWord"
          class="searchFileInput"
          type="text"
          placeholder="请输入文件名"
        />
        <Icon
          v-show="searchFileKeyWord"
          class="clearSearchFileInputBtn"
          icon="codex:cross"
          @click="clearSearchFileInput"
        />
      </div>
      <div class="dirListBox">
        <div
          v-for="(item, index) in fileList_filtered"
          :id="`file-${index}`"
          :key="item.filePath"
          class="dirItem"
          spellcheck="false"
          :class="currentFilePath === item.filePath ? 'activeDirItem' : ''"
          :contenteditable="item.editable"
          @click="openFile(item)"
          @contextmenu.prevent="showContextMenu(item.filePath)"
          @blur="saveFileName(item, index)"
          @keydown.enter.prevent="saveFileName_enter(index)"
        >
          {{ item.fileName.slice(0, -3) }}
        </div>
      </div>
    </div>
    <!-- 编辑区 -->
    <div class="editorPanel">
      <div class="btnBox">
        <div
          v-for="(config, key) in actionDic"
          :key="key"
          class="btnItem"
          :title="config.label"
          @click="config.action"
        >
          <Icon :icon="config.icon" />
        </div>
      </div>
      <textarea
        ref="editorRef"
        v-model="markdownContent"
        spellcheck="false"
        class="editor"
        :class="{ dragging: isDragging }"
        placeholder="请输入内容 ( Markdown语法 ) ..."
        @keydown="handleKeyDown"
        @contextmenu.prevent="show_edite_contextMenu"
        @dragover="handleDragOver"
        @dragleave="handleDragLeave"
        @drop="handleDrop"
        @scroll="syncPreviewScroll"
      ></textarea>
    </div>
    <!-- 拖拽提示层 -->
    <div v-if="isDragging" class="drag-overlay">
      <div class="drag-message">{{ dragMessage }}</div>
    </div>
    <!-- 大纲 -->
    <div v-if="currentFilePath && outlineList.length" class="outlinePanel">
      <div class="panelTitle">大纲</div>
      <div class="searchTitleBox">
        <Icon class="searchTitleInputIcon" icon="material-symbols-light:search" />
        <input
          v-model="searchTitleKeyWord"
          class="searchTitleInput"
          type="text"
          placeholder="请输入标题"
        />
        <Icon
          v-show="searchTitleKeyWord"
          class="clearSearchTitleInputBtn"
          icon="codex:cross"
          @click="clearSearchTitleInput"
        />
      </div>
      <div class="outlineListBox">
        <template v-for="(item, index) in outlineList_filtered">
          <div
            v-if="!item.hide"
            :key="index"
            :title="item.text"
            class="outlineItem"
            :style="{ color: currentOutLineID === item.id ? 'red' : '' }"
            @click="handle_clickOutLine(item)"
          >
            <div v-for="i in item.level" :key="i">
              <Icon
                v-if="
                  index < outlineList_filtered.length - 1 &&
                  item.level < outlineList_filtered[index + 1].level &&
                  i === item.level
                "
                :icon="
                  item.collapsed ? 'iconamoon:arrow-right-2-light' : 'iconamoon:arrow-down-2-light'
                "
                @click.stop="toggleCollapse(item, index)"
              />
              <Icon v-else icon="" />
            </div>
            <div style="margin-left: 4px">
              {{ item.text }}
            </div>
          </div>
        </template>
      </div>
    </div>
    <!-- 效果预览 -->
    <div class="previewBox">
      <div class="panelTitle">最终效果</div>
      <div
        ref="previewRef"
        class="preview"
        @click="handle_preview_click"
        @scroll="update_currentOutlineId"
        v-html="parsedMarkdown"
      ></div>
    </div>
  </div>
</template>
<style scoped>
.page {
  width: 100%;
  height: 100%;
  overflow: hidden;
  display: flex;
  padding: 10px;
  box-sizing: border-box;
}
.dirPanel {
  width: 200px;
  border: 1px solid gray;
}
.dirListBox {
  padding: 0px 10px 10px 10px;
}
.dirItem {
  padding: 6px;
  font-size: 12px;
  cursor: pointer;
  border-radius: 4px;
  margin-bottom: 6px;
}
.searchFileBox {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
}
.searchFileInput {
  display: block;
  font-size: 12px;
  padding: 4px 20px;
}
.searchFileInputIcon {
  position: absolute;
  font-size: 16px;
  transform: translateX(-80px);
}
.clearSearchFileInputBtn {
  position: absolute;
  cursor: pointer;
  font-size: 16px;
  transform: translateX(77px);
}
.activeDirItem {
  background-color: #e4e4e4;
}
.panelTitle {
  font-size: 16px;
  font-weight: bold;
  text-align: center;
  background-color: #f0f0f0;
  height: 34px;
  line-height: 34px;
}
.previewBox {
  flex: 1;
  min-width: 0;
  border: 1px solid gray;
  border-left: none;
  display: flex;
  flex-direction: column;
}
.preview {
  flex: 1;
  overflow: auto;
  padding: 10px;
  white-space: pre-wrap;
  word-wrap: break-word;
  line-height: 1.5;
}
.editorPanel {
  flex: 1;
  border: 1px solid gray;
  border-left: none;
  display: flex;
  flex-direction: column;
  width: 620px;
}
.editor {
  padding: 10px;
  border: none;
  box-sizing: border-box;
  flex: 1;
  word-break: break-all;
  resize: none;
  font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
  font-size: 14px;
  line-height: 1.5;
  outline: none;
}
/* 滚动条样式优化 */
.editor,
.preview,
.outlineListBox {
  scrollbar-width: thin;
  scrollbar-color: #c1c1c1 #ffffff;
  scrollbar-gutter: stable;
}
.btnBox {
  display: flex;
  justify-content: space-evenly;
  background-color: #f0f0f0;
  height: 34px;
}
.btnItem {
  cursor: pointer;
  background-color: #f0f0f0;
  padding: 6px;
  font-size: 20px;
  display: inline-block;
}
.btnItem:hover {
  background-color: #e0e0e0;
}
/* 大纲的样式 */
.outlinePanel {
  width: 200px;
  border: 1px solid gray;
  border-left: none;
  display: flex;
  flex-direction: column;
  font-size: 14px;
}
.searchTitleBox {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
}
.searchTitleInput {
  display: block;
  font-size: 12px;
  padding: 4px 20px;
}
.searchTitleInputIcon {
  position: absolute;
  font-size: 16px;
  transform: translateX(-80px);
}
.clearSearchTitleInputBtn {
  position: absolute;
  cursor: pointer;
  font-size: 16px;
  transform: translateX(77px);
}
.outlineListBox {
  padding: 10px;
  line-height: 1.5;
  flex: 1;
  overflow-y: auto;
}
.outlineItem {
  cursor: pointer;
  text-wrap: nowrap;
  display: flex;
  align-items: center;
  color: #787474;
  margin-bottom: 6px;
}
/* 编辑器-右键菜单样式 */
.context-menu {
  position: fixed;
  z-index: 1000;
  width: 200px;
  background-color: white;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  padding: 4px 0;
  font-size: 12px;
}
.context-menu ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.context-menu li {
  padding: 8px 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
}
.context-menu li:hover {
  background-color: #e0e0e0;
}
.context-menu li i {
  margin-right: 8px;
  text-align: center;
}
.context-menu-btnBox {
  display: flex;
}
.context-menu-btn {
  flex: 1;
  padding: 8px 4px;
  cursor: pointer;
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
}
.context-menu-btn:hover {
  background-color: #e0e0e0;
}
.editor.dragging {
  border-color: #2196f3;
  background-color: #f5f8ff;
}
.drag-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: rgba(33, 150, 243, 0.1);
  border: 1px dashed #2196f3;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none; /* 允许点击穿透到 textarea */
  box-sizing: border-box;
}
.drag-message {
  font-size: 16px;
  color: #2196f3;
  background-color: white;
  padding: 10px 20px;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>
